<?php
///////////////////////////////////////////////////////////////////////////////////////////////////////
//  这个文件是 JCAT PHP框架的一部，该项目和此文件 均遵循 GNU 自由软件协议
// 
//  Copyleft 2008 JeCat.cn(http://team.JeCat.cn)
//
//
//  JCAT PHP框架 的正式全名是：Jellicle Cat PHP Framework。
//  “Jellicle Cat”出自 Andrew Lloyd Webber的音乐剧《猫》（《Prologue:Jellicle Songs for Jellicle Cats》）。
//  JCAT 是一个开源项目，它像音乐剧中的猫一样自由，你可以毫无顾忌地使用JCAT PHP框架。JCAT 由中国团队开发维护。
//  正在使用的这个版本是：0.5.0 / SVN信息: $Id: class.JCAT_ORMRule_FieldsToProperties.php 2041 2009-11-07 11:20:37Z aleechou $
//
//
//
//  相关的链接：
//    [主页] http://jcat.JeCat.cn
//    [下载(HTTP)] http://code.google.com/p/jcat-php/downloads/list
//    [下载(svn)] svn checkout http://jcat-php.googlecode.com/svn/branches/0.4.0/Framework/ JCAT0.4
//    [在线文档] http://jcat.JeCat.cn/document
//    [社区] http://jj.jecat.cn/forum-7-1.html
//  不很相关：
//    [MP3] http://www.google.com/search?q=jellicle+songs+for+jellicle+cats+Andrew+Lloyd+Webber
//    [VCD/DVD] http://www.google.com/search?q=CAT+Andrew+Lloyd+Webber+video
//
///////////////////////////////////////////////////////////////////////////////////////////////////////
/*-- Project Introduce --*/






/**
 * What's this Class ?
 *
 * @author		alee
 * @access		public
 */
class JCAT_ORMRule_FieldsToProperties extends JCAT_ORMRuleBase
				implements JCAT_IORMRule_FieldsToProperties
{
	const CONFIG_TYPE = 'hub' ;
	const SUBSQL_TABLES_JOIN = '__SUBSQL_TABLES_JOIN' ;
	
	/**
	 * Description
	 *
	 * @access	public
	 * @static
	 * @return	string
	 */
	static public function GetConfigType()
	{
		return self::CONFIG_TYPE ;
	}

	/**
	 * Description
	 *
	 * @access	public
	 * @array	$arrConfig		array
	 * @array	$aDB				JCAT_DB
	 * @static
	 * @return	JCAT_IORMRule
	 */
	public function InitFromConfig(array $arrConfig,JCAT_DB $aDB)
	{		
		$arrConfig = JCAT_ORMRuleBase::TidyORMConfArray($arrConfig) ;
		
		$aDBConn = $aDB->GetConnect() ;

		// set primary key
		// ----------------------------------------------------------------------------------
		if(empty($arrConfig['primarykey']))
		{
			$sPrimaryKey = $aDBConn->GetDBReflector()->GetPrimaryKey($arrConfig['table']) ;
			if($sPrimaryKey)
			{
				$this->SetPrimaryKey($sPrimaryKey) ;
			}
		}
		
		else 
		{
			$this->SetPrimaryKey($arrConfig['primarykey']) ;
		}
		
		// fields 
		// ----------------------------------------------------------------------------------
		// reflection db to get all fields of table
		if( empty($arrConfig['fields']) or $arrConfig['fields']=='*' )
		{
			$arrColumns = $aDBConn->GetDBReflector()->QueryColumns($arrConfig['table']) ;
			
			foreach ($arrColumns as $sColumnName)
			{
				$this->AddField($sColumnName) ;
			}
		}
		else 
		{
			if(!is_array($arrConfig['fields']))
			{
				throw new JCAT_Exception(JCAT_Language::SentenceEx(
					'ORMConfig fields 元素必须是一个数组'
					, 'JCAT', null
				)) ;
			}
		
			foreach ($arrConfig['fields'] as $sColumnName)
			{
				$this->AddField($sColumnName) ;
			}
		}
		
		// property mapping
		if( !empty($arrConfig['properties']) )
		{
			if(!is_array($arrConfig['properties']))
			{
				throw new JCAT_Exception(JCAT_Language::SentenceEx(
					'ORMConfig properties 元素必须是一个数组'
					, 'JCAT', null
				)) ;
			}
			
			foreach ($arrConfig['properties'] as $sPropertyName=>$sFieldName)
			{
				$this->AddField($sFieldName,$sPropertyName) ;
			}
		}
	}

	/**
	 * action for construction
	 *
	 * @access	public
	 * @return	void
	 */
	public function OnConstruct(JCAT_IDBModel $aModel)
	{
		$arrFieldsProperties = array() ;
		
		// set insert data
		foreach ($this->arrFieldsToPropertiesMappings as $sPropertyName=>$sFieldName)
		{
			if( $aModel->IsDataExists($sPropertyName) )
			{
				continue ;
			}
			
			// 重复的 property => field 
			if( isset($arrFieldsProperties[$sFieldName]) )
			{
				$aModel->SetDataAlias($arrFieldsProperties[$sFieldName],$sPropertyName) ;
			}
			
			else 
			{
				$aModel->Set($sPropertyName,JCAT_Model::NULL_DATA_VALUE,true,false) ;
				$arrFieldsProperties[$sFieldName] = $sPropertyName ;
			}
		}
	}
	
	/**
	 * action for delete
	 *
	 * @access	public
	 * @param	$aModel					JCAT_IDBModel
	 * @param	JCAT_DBSQLInsert		$aSQLInsert
	 * @return	void
	 */
	public function InsertPreprocess(JCAT_IDBModel $aModel,JCAT_DBSQLInsert $aSQLInsert)
	{
		// set which table will be inserted
		$aSQLInsert->SetTableName($this->GetTableName()) ;
		
		// set insert data
		foreach ($this->arrFieldsToPropertiesMappings as $sPropertyName=>$sFieldName)
		{
			$sData = $aModel->Get($sPropertyName) ;
			
			if($sData!==NULL)
			{
				$aSQLInsert->SetData($this->GetTableName().'.'.$sFieldName,$sData) ;
			}
		}
	}
	
	/**
	 * action for delete
	 *
	 * @access	public
	 * @param	$aModel			JCAT_IDBModel
	 * @param	$nInsertId		int
	 * @return	void
	 */
	public function OnAfterInsert(JCAT_IDBModel $aModel,$nInsertId)
	{
		$sProperty = $this->GetPropertyByField($this->GetPrimaryKey()) ;
		$aModel->Set( $sProperty, $nInsertId ) ;
	}
	
	/**
	 * before select, ORM will make a sql select by evrey ORMRule's OnSelectPreprocess() method
	 *
	 * @access	public
	 * @param	$aModel			JCAT_IDBModel
	 * @param	$aSQLSelect		JCAT_DBSQLSelect
	 * @return	void
	 */
	public function SelectPreprocess(JCAT_IDBModel $aModel,JCAT_DBSQLSelect $aSQLSelect)
	{
		$sTable = $this->GetTableName() ;
		
		// add db table to sub sql 'FROM' of sql select
		$aTableJoin = $this->GetTableJoin($aSQLSelect) ;
		$aTableJoin->AddTable($sTable) ;
		
		// add return columns
		$this->AddReturnColumns($aSQLSelect) ;
	}
	
	/**
	 * Description
	 *
	 * @access	public
	 * @param	$Parameter
	 * @return	void
	 */
	public function AddReturnColumns(JCAT_DBSQLSelect $aSQLSelect)
	{
		$sTable = $this->GetTableName() ;
		
		// get return column list of SQL select 
		$aSQLReturnLst = $aSQLSelect->GetSubSQLReturnColumnList() ;
		
		// add return column
		$aDBFactory = $aSQLSelect->GetFactory() ;
		$arrFields = array_unique($this->arrFieldsToPropertiesMappings) ;
		foreach ($arrFields as $sFieldName)
		{
			$aColumn = $aDBFactory->CreateReturnColumn("{$sTable}.{$sFieldName}") ;
			$aSQLReturnLst->AddReturnColumn($aColumn) ;
		}
	}
	
	/**
	 * Description
	 *
	 * @access	private
	 * @param	$aSQLSelect		JCAT_DBSQLSelect
	 * @return	JCAT_DBTableJoin
	 */
	private function GetTableJoin(JCAT_DBSQLSelect $aSQLSelect)
	{
		$aTableJoin = $aSQLSelect->GetObjectByAlias(self::SUBSQL_TABLES_JOIN) ;
		if(!$aTableJoin)
		{
			$aTableJoin = $aSQLSelect->GetFactory()->CreateTableJoin($this->GetTableName()) ;
			$aSQLSelect->GetSubSQLFrom()->AddTable($aTableJoin) ;
			$aSQLSelect->SetObjectAlias($aTableJoin,self::SUBSQL_TABLES_JOIN);
		}
		
		return $aTableJoin ;
	}
		
	/**
	 * action for select
	 *
	 * @access	public
	 * @param	$aModel			JCAT_IDBModel
	 * @param	$aRecordSet		JCAT_DBRecordSet
	 * @return	void
	 */
	public function OnAfterSelect(JCAT_IDBModel $aModel,JCAT_DBRecordSet $aRecordSet)
	{
		$sTable = $this->GetTableName() ;
		foreach ($this->arrFieldsToPropertiesMappings as $sPropertyName=>$sFieldName)
		{
			$sFieldData = $aRecordSet->GetField("{$sTable}.{$sFieldName}") ;
			if($sFieldData===null)
			{
				$sFieldData = $aRecordSet->GetField($sFieldName) ;
			}
			$aModel->Set($sPropertyName,$sFieldData) ;
		}
	}
	
	/**
	 * action for update
	 *
	 * @access	public
	 * @param	$aModel			JCAT_IDBModel
	 * @param	$aDBTrans		JCAT_IDBTransaction
	 * @param	$aSQLUpdate		JCAT_DBSQLUpdate
	 * @return	void
	 */
	public function OnUpdate(JCAT_IDBModel $aModel,JCAT_IDBTransaction $aDBTrans,JCAT_DBSQLUpdate $aSQLUpdate)
	{
		// add db table to sub sql 'FROM' of sql update
		$aSubSQLFrom = $aSQLUpdate->GetSubSQLFrom() ;
		$aSubSQLFrom->AddTable($this->GetTableName()) ;
		
		// 切换 数据表字段 和 Model属性值， （移除重复的 属性=>字段）
		$arrFieldsToProperty = array_flip($this->arrFieldsToPropertiesMappings) ;
		
		// set update data
		foreach ($arrFieldsToProperty as $sFieldName=>$sPropertyName)
		{
			$sData = $aModel->Get($sPropertyName) ;
			
			if($sData!==NULL)
			{
				$aSQLUpdate->SetData($this->GetTableName().'.'.$sFieldName,$sData) ;
			}
		}
		
		// set where sub sql
		$this->SetWhere($aModel,$aSQLUpdate) ;
	}
	
	/**
	 * action for delete
	 *
	 * @access	public
	 * @param	$aModel			JCAT_IDBModel
	 * @param	$aDBTrans		JCAT_IDBTransaction
	 * @param	$aSQLDelete		JCAT_DBSQLDelete
	 * @return	void
	 */
	public function OnDelete(JCAT_IDBModel $aModel,JCAT_IDBTransaction $aDBTrans,JCAT_DBSQLDelete $aSQLDelete)
	{		
		// add db table to sub sql 'FROM' of sql select
		$aSubSQLFrom = $aSQLDelete->GetSubSQLFrom() ;
		$aSubSQLFrom->AddTable($this->GetTableName()) ;
		
		// set where sub sql
		$this->SetWhere($aModel,$aSQLDelete) ;
	}
	
	/**
	 * Description
	 *
	 * @access	private
	 * @param	$Parameter
	 * @return	void
	 */
	private function SetWhere(JCAT_IDBModel $aModel,JCAT_DBSQLRecordsOperate $aSQL)
	{
		$aFactory = $aSQL->GetFactory() ;
		$aSubSQLWhere = $aSQL->GetSubSQLWhere() ;
		$sPrimaryKey = $this->GetPrimaryKey() ;
		$sPrimaryKeyProp = $this->GetPropertyByField($sPrimaryKey) ;
		$aSubSQLWhere->AddCondition( $aFactory->CreateColumn($sPrimaryKey,$this->GetTableName()), $aModel->Get($sPrimaryKeyProp) ) ;
	}
	
	
	/**
	 * Description
	 *
	 * @access	public
	 * @return	void
	 */
	public function AddField($sDBFieldName,$sModelPropertyName=null)
	{
		if( $sModelPropertyName===null )
		{
			$sModelPropertyName = $sDBFieldName ;
		}
		
		JCAT_ASSERT::ASSERT_STRING($sDBFieldName);
		JCAT_ASSERT::ASSERT_STRING($sModelPropertyName);
		
		$this->arrFieldsToPropertiesMappings[$sModelPropertyName] = $sDBFieldName ;
	}
	
	/**
	 * get db field by property name of model
	 *
	 * @access	public
	 * @param	$sModelPropertyName		string
	 * @return	string
	 */
	public function GetFieldByProperty($sModelPropertyName)
	{
		return isset($this->arrFieldsToPropertiesMappings[$sModelPropertyName])?
				 $this->GetTableName().'.'.$this->arrFieldsToPropertiesMappings[$sModelPropertyName]: null ;
	}
	
	/**
	 * get property name of model by db field
	 *
	 * @access	public
	 * @param	$sDBFieldName		string
	 * @return	string
	 */
	public function GetPropertyByField($sDBFieldName)
	{
		@list($sTableName,$sDBFieldName) = explode('.',$sDBFieldName) ;
		if(!$sDBFieldName)
		{
			$sDBFieldName = $sTableName ;
		}
		
		else 
		{
			// does not field for this table
			if($sTableName!=$this->GetTableName())
			{
				return null ;
			}
		}
		
		$sPropertyName = array_search($sDBFieldName,$this->arrFieldsToPropertiesMappings) ;
		return ($sPropertyName===false)? null: $sPropertyName ;
	}
	

	/**
	 * Description
	 *
	 * @access	public
	 * @param	$sPrimaryKey		string
	 * @return	void
	 */
	public function SetPrimaryKey($sPrimaryKey)
	{
		$this->sPrimaryKey = $sPrimaryKey ;
	}


	/**
	 * Description
	 *
	 * @access	public
	 * @return	string
	 */
	public function GetPrimaryKey()
	{
		return $this->sPrimaryKey ;
	}
	
	/**
	 * mappings of db fields to object properties
	 * 
	 * @access	private
	 * @var		array
	 */
	private $arrFieldsToPropertiesMappings = array() ;
	
	/**
	 * Description
	 * 
	 * @access	private
	 * @var		string
	 */
	private $sPrimaryKey ;
	
}
?>